package cn.lili.modules.file.plugin.impl;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.properties.ProfileProperties;
import cn.lili.modules.file.entity.enums.OssEnum;
import cn.lili.modules.file.plugin.FilePlugin;
import cn.lili.modules.system.entity.dto.OssSetting;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.*;
import io.minio.errors.MinioException;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.stream.Collectors;


/**
 * MINIO文件插件
 *
 * @author liushuai(liushuai711 @ gmail.com)
 * @version v4.0
 * @Description:
 * @since 2022/6/6 17:45
 */
@Slf4j
public class MinioFilePlugin implements FilePlugin {

    private OssSetting ossSetting;

    public MinioFilePlugin(OssSetting ossSetting) {
        this.ossSetting = ossSetting;
    }

    /**
     * 桶占位符
     */
    private static final String BUCKET_PARAM = "${bucket}";
    /**
     * bucket权限-只读
     */
    private static final String READ_ONLY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
    /**
     * bucket权限-只读
     */
    private static final String WRITE_ONLY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
    /**
     * bucket权限-读写
     */
    private static final String READ_WRITE = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";


    private MinioClient minioClient;


    @Override
    public OssEnum pluginName() {
        return OssEnum.MINIO;
    }

    @Override
    public String pathUpload(String filePath, String key) {
        try {
            return this.inputStreamUpload(new FileInputStream(filePath), key);
        } catch (Exception e) {
            throw new ServiceException(ResultCode.OSS_DELETE_ERROR, e.getMessage());
        }
    }

    @Override
    public String inputStreamUpload(InputStream inputStream, String key) {
        return inputStreamUpload(inputStream, key,  ossSetting.getM_bucketName());
    }


    @Override
    public String inputStreamUpload(InputStream inputStream, String key, String bucket) {
        // MEMBER/1719524736256118784/b4367b522027400fbce866af4ee9686d.jpg
        // 获取扩展名
        String extension = key.substring(key.lastIndexOf("."));
        String mimeType = getMimeType(extension);
        try {
            MinioClient client = getOssClient();
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucket).stream(inputStream, inputStream.available(), 5 * 1024 * 1024)
                    .object(key)
                    .contentType(mimeType)
                    .build();
            client.putObject(putObjectArgs);
        } catch (Exception e) {
            log.error("上传失败2，", e);
            throw new ServiceException(ResultCode.OSS_DELETE_ERROR, e.getMessage());
        }
        //拼接出可访问的url地址
        return ossSetting.getM_frontUrl() + "/" + bucket + "/" + key;
    }


    @Override
    public void deleteFile(List<String> key) {
        if (key == null || key.isEmpty()) {
            return;
        }
        MinioClient ossClient = getOssClient();
        List<DeleteObject> objectList = key.stream().map(DeleteObject::new).collect(Collectors.toList()); Iterable<Result<DeleteError>> results =
                ossClient.removeObjects(RemoveObjectsArgs.builder().objects(objectList).bucket(ossSetting.getM_bucketName()).build());
        for (Result<DeleteError> result : results) {
            DeleteError error = null;
            try {
                error = result.get();
                log.error(
                        "Error in deleting object " + error.objectName() + "; " + error.message());
            } catch (Exception e) {

                log.error(
                        "Error in deleting object " + e.getMessage());
            }
        }
    }


    /**
     * 获取oss client
     *
     * @return
     */
    private MinioClient getOssClient() {
        if (minioClient != null) {
            return this.minioClient;
        }
        synchronized (this) {
            if (minioClient == null) {
                //创建客户端
                this.minioClient = MinioClient.builder()
                        .endpoint(ossSetting.getM_endpoint())
                        .credentials(ossSetting.getM_accessKey(), ossSetting.getM_secretKey())
                        .build();
                try {
                    //查看对应的bucket是否已经存在，不存在则创建
                    if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(ossSetting.getM_bucketName()).build())) {
                        //创建bucket
                        MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(ossSetting.getM_bucketName()).build();
                        this.minioClient.makeBucket(makeBucketArgs);
                        setBucketPolicy(this.minioClient, ossSetting.getM_bucketName(), "read-write" );
                        log.info("创建minio桶成功{}", ossSetting.getM_bucketName());
                    }
                } catch (Exception e) {
                    //晴空配置
                    minioClient = null;
                    log.error("创建[{}]bucket失败", ossSetting.getM_bucketName());
                    throw new ServiceException(ResultCode.OSS_DELETE_ERROR, e.getMessage());
                }
            }
        }
        return minioClient;
    }


    /**
     * 更新桶权限策略
     *
     * @param bucket 桶
     * @param policy 权限
     */
    public static void setBucketPolicy(MinioClient client, String bucket, String policy) throws Exception {
        switch (policy) {
            case "read-only":
                client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).config(READ_ONLY.replace(BUCKET_PARAM, bucket)).build());
                break;
            case "write-only":
                client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).config(WRITE_ONLY.replace(BUCKET_PARAM, bucket)).build());
                break;
            case "read-write":
                client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucket).region("public" ).config(READ_WRITE.replace(BUCKET_PARAM, bucket)).build());
                break;
            case "none":
            default:
                break;
        }
    }




    /**
     * 从一个bucket 拷贝到 另一个 bucket
     * 没有发生拷贝返回 null ，拷贝成功 返回 新文件url
     *
     * @param url    路径
     * @param bucket 新的bucket
     */
    @Override
    public String copyFile(String url, String bucket) {
        String activeProfile = ProfileProperties.getActiveProfile();
        if (ObjectUtil.isEmpty(bucket)) {
            throw new ServiceException("MinioClient 文件拷贝错误 新的bucket为空！ ");
        }

        if (CharSequenceUtil.isEmpty(activeProfile)) {
            activeProfile = "";
        }
        if (CharSequenceUtil.isNotEmpty(activeProfile) && !activeProfile.endsWith("/")) {
            activeProfile = activeProfile + "/";
        }
        if (CharSequenceUtil.isNotEmpty(url)) {
            String oldBucket = getBucketByUrl(url);
            if (!oldBucket.equals(bucket) || ObjectUtil.isNotEmpty(activeProfile)) {
                String name = this.getName(url, oldBucket);
                String newUrl = name;
                // 避免因修改请求导致环境变量路径重复
                if (!name.startsWith(activeProfile)) {
                    newUrl = activeProfile + name;
                }
                newUrl = this.getMinioFileName(bucket, newUrl);
                newUrl = this.copy(url, oldBucket, newUrl, bucket);
                return newUrl;
            }
        }
        return url;
    }

    /**
     * 拷贝
     *
     * @param fileName
     * @param destFileName 相对路径 的文件名
     * @param destBucket   新的Bucket
     */
    public String copy(String fileName, String destFileName, String destBucket) {
        String bucket = getBucketByUrl(fileName);
        return copy(fileName, bucket, destFileName, destBucket, false);
    }

    /**
     * 拷贝
     *
     * @param fileName
     */
    public String copy(String fileName, String bucket, String destFileName, String destBucket) {
        return copy(fileName, bucket, destFileName, destBucket, false);
    }


    /**
     * 拷贝
     *
     * @param fileName
     */
    public String copy(String fileName, String bucket, String destFileName, String destBucket, boolean rm) {
        Assert.notNull(destBucket,"文件拷贝错误：参数：destBucket 不能为空！");
        Assert.notNull(bucket,"文件拷贝错误：参数：bucket 不能为空！");
        Assert.notNull(fileName,"文件拷贝错误：参数：fileName 不能为空！");
        try {
            // bucket一致，不处理
            if (CharSequenceUtil.equals(bucket, destBucket)) {
                return fileName;
            }
            // 路径一致，不处理
            if (CharSequenceUtil.equals(fileName, destFileName)) {
                return fileName;
            }
            String name = getName(fileName, bucket);
            String destName = "";
            if (ObjectUtil.isNotEmpty(destFileName)) {
                destName = getName(destFileName, destBucket);
                /*if (destName.equals(name)) {
                    //路径一致不处理
                    return fileName;
                }*/
            } else {
                destName = name;
            }
            CopySource source = CopySource.builder().bucket(bucket).object(name).build();
            getOssClient().copyObject(CopyObjectArgs.builder().bucket(destBucket).object(destName).source(source).build());
            if (rm) {
                remove(fileName, bucket);
            }
            return getMinioFileName(destBucket, destName);
        } catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) {
            log.error("MinioClient copy Error occurred,fileName: " + fileName, e);
            throw new ServiceException("MinioClient 文件拷贝错误 " + e.getMessage());
        }
    }

    /**
     * 文件删除
     *
     * @param url
     * @param bucket null 使用正式bucket
     */
    public void remove(String url, String bucket) {
        if (StrUtil.isEmpty(bucket)) {
            bucket = getBucketByUrl(url);
        }
        try {
            String name = getName(url, bucket);
            getOssClient().removeObject(RemoveObjectArgs.builder().bucket(bucket).object(name).build());
        } catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) {
            log.error("MinioClient remove Error occurred,fileName: " + url, e);
            throw new ServiceException("MinioClient 文件删除错误 " + e.getMessage());
        }
    }


    /**
     * 根据路径获取 Bucket
     *
     * @param url
     * @return
     */
    public String getBucketByUrl(String url) {
        try {
            if (ObjectUtil.isNotEmpty(url)) {
                URL uri = URLUtil.url(url);
                String path = uri.getPath();
                if (ObjectUtil.isNotEmpty(path)) {
                    if (path.startsWith("/")) {
                        path = path.substring(1);
                    }
                    if (ObjectUtil.isNotEmpty(path)) {
                        String[] split = path.split("/");
                        return split[0];
                    }
                }
            }
        } catch (Exception e) {
            log.error("MinioClient getBucketByUrl Error occurred,fileName: " + url, e);

        }
        throw new ServiceException("未获取到url对应的bucket信息！");
    }


    /**
     * 获取除去bucket 的相对路径名称
     *
     * @param fileName
     * @param bucket
     * @return
     */
    private String getName(String fileName, String bucket) {
        if (ObjectUtil.isNotEmpty(fileName)) {
            return fileName.replace(StrUtil.format("{}/{}/", ossSetting.getM_endpoint(), bucket), "");
        }
        return fileName;
    }


    /**
     * 获取Minio文件名
     *
     * @param bucket
     * @param filename
     * @return
     */
    private String getMinioFileName(String bucket, String filename) {
        return StrUtil.format("{}/{}/{}", ossSetting.getM_endpoint(), bucket, filename);
    }

    /**
     * 根据扩展名获取 mimeType
     *
     * @param extension 扩展名
     * @return mimeType
     */
    private String getMimeType(String extension) {
        if (CharSequenceUtil.isBlank(extension)) {
            extension = "";
        }
        // 根据扩展名去除 mimeType
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        // 通用mimeType，字节流
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
        if (ObjectUtil.isNotEmpty(extensionMatch)) {
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }


}
